{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Simulating noise on Amazon Braket\n",
    "\n",
    "This notebook gives a detailed overview of noise simulations on Amazon Braket. Amazon Braket provides two noise simulators: a local noise simulator that you can use for free as part of the Braket SDK and a fully managed, high-performing noise simulator, DM1. Both simulators are based on the density matrix formalism. After this tutorial, you will be able to define noise channels, apply noise to new or existing circuits, and run those circuits on the Braket noise simulators. \n",
    "\n",
    "### Table of contents:\n",
    "* [Background](#Background)\n",
    "    * [Noise simulation based on the density matrix formalism](#density_matrix)\n",
    "    * [Quantum channel and Kraus representation](#quantum_channel)\n",
    "* [General imports](#imports)\n",
    "* [Quick start](#start)\n",
    "* [Defining noise channels](#noise_channels)\n",
    "    * [Pre-defined noise channels](#pre-defined)\n",
    "    * [Defining custom noise channels](#self-defined)\n",
    "* [Adding noise to a circuit](#apply_noise)\n",
    "    * [Build noisy circuits bottom-up](#apply_noise_directly)\n",
    "    * [Applying noise to existing circuits with global methods](#apply_noise_globally)\n",
    "        * [Applying gate noise to the circuit](#gate-noise)\n",
    "        * [Applying initialization noise to the circuit](#initialization-noise)\n",
    "        * [Applying readout noise to the circuit](#readout-noise)\n",
    "    * [Using both the direct and global methods to apply noise](#both)\n",
    "* [Running a noisy circuit](#run)\n",
    "\n",
    "### Resources\n",
    "* [Blog] https://aws.amazon.com/about-aws/whats-new/2021/05/amazon-braket-introduces-quantum-circuit-noise-simulator-dm1/"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Background <a class=\"anchor\" id=\"Background\"></a>\n",
    "\n",
    "### Noise simulation based on the density matrix formalism <a class=\"anchor\" id=\"density_matrix\"></a>\n",
    "In an ideal case, a quantum state prepared by a noise-free circuit can be described by a state vector $|\\psi\\rangle$ -- we call it a 'pure state'. However, the presence of noise in realistic quantum devices will introduce classical uncertainty to the quantum state. For example, a bit flip error with 50% probability acting on a qubit flips the $|0\\rangle$ state into either $|0\\rangle$ or $|1\\rangle$ with a 50-50 chance. Note that this is different from an Hadamard-gate acting on $|0\\rangle$: The latter results in a coherent superposition of $|0\\rangle$ and $|1\\rangle$, whereas the former is a classical, so-called mixture of $|0\\rangle$ and $|1\\rangle$. The most general way of describing a quantum state in the presence of noise is through the so-called density matrix: $\\rho = \\sum_i p_i|\\psi_i\\rangle\\langle\\psi_i|$. It can be understood as a classical mixture of a series of pure states $|\\psi_i\\rangle$ (each of which could be highly entangled), where $p_i$ is the probability of the state being in $|\\psi_i\\rangle$. Because the $p_i$ are classical probabilities they have to sum up to 1: $\\sum_i p_i = 1$. The density matrix of a pure state is simply $\\rho = |\\psi\\rangle\\langle\\psi|$ and, in the bit-flip example from above, the density matrix would be $\\rho = 0.5|0\\rangle\\langle 0| + 0.5|1\\rangle\\langle 1|$. \n",
    "\n",
    "The density matrix formalism is a very useful way to describe a noisy system with probabilistic outcomes. It gives an exact description of a quantum system going through a quantum channel with noise. Besides, the expectation value of an observable $\\langle O\\rangle$ can be easily calculated by $\\rm{Tr}(O\\rho)$, where \"$\\rm{Tr}$\" is the trace operator. \n",
    "\n",
    "### Quantum channel and Kraus representation <a class=\"anchor\" id=\"quantum_channel\"></a>\n",
    "\n",
    "A [quantum channel](https://en.wikipedia.org/wiki/Quantum_channel) describes the time evolution of a quantum state which is expressed as a density matrix. For instance, to understand what a series of noisy gates does to the state of a quantum computer, you can apply a quantum channel corresponding to the different gate and noise operations. \n",
    "Mathematically speaking, a quantum channel is a completely positive and trace-preserving (CPTP) linear map acting on a density matrix. Completely positive means the channel maps positive operators into positive operators (even if the operator is applied to part of a larger system) to make sure the density matrix describes a proper quantum state after the map. Trace-preserving means the trace of the density matrix remains unchanged during the mapping process (this is so that after the map the classical probabilities $p_i$ still sum to 1). \n",
    "\n",
    "The so-called _Kraus representation_ is a commonly used representation for CPTP maps. [Kraus's theorem](https://en.wikipedia.org/wiki/Quantum_operation#Kraus_operators) states that any quantum operation acting on a quantum state $\\rho$ can be expressed as a map $\\varepsilon(\\rho) = \\sum_i K_i\\rho K_i^{\\dagger}$, and it satisfies: $\\sum_i K_i^{\\dagger}K_i = \\mathbb{1}$, where $\\mathbb{1}$ is the Identity operator.\n",
    "\n",
    "Let's get started and have a look how you can define and simulate noisy circuits on Amazon Braket."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## General imports <a class=\"anchor\" id=\"imports\"></a>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's begin with the usual imports and setting our s3 location where we want to persist results."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "from braket.circuits import Circuit, Observable, Gate, Noise\n",
    "from braket.devices import LocalSimulator\n",
    "from braket.aws import AwsDevice\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from scipy.stats import unitary_group"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-block alert-info\">\n",
    "    <b>Note</b> Enter your desired S3 location (bucket and prefix). Remember that bucket names for Amazon Braket always begin with \"amazon-braket-\". \n",
    "</div>\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# enter the S3 bucket you created during onboarding (or any other bucket starting with \"amazon-braket-\") \n",
    "my_bucket = # TODO:  Fill in the name of the bucket\n",
    "my_prefix = \"demo\" # the name of the folder in the bucket\n",
    "s3_folder = (my_bucket, my_prefix)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Quick start <a class=\"anchor\" id=\"start\"></a>\n",
    "\n",
    "Let's start with a simple example of running a noisy circuit on Amazon Braket. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "measurement results: Counter({'00': 424, '11': 400, '10': 88, '01': 88})\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# build a simple circuit\n",
    "circ = Circuit().h(0).cnot(0,1)\n",
    "\n",
    "# define a noise channel\n",
    "noise = Noise.BitFlip(probability=0.1)\n",
    "\n",
    "# add noise to every gate in the circuit\n",
    "circ.apply_gate_noise(noise)\n",
    "\n",
    "# select the local noise simulator\n",
    "device = LocalSimulator('braket_dm')\n",
    "\n",
    "# run the circuit on the local simulator\n",
    "task = device.run(circ, shots = 1000)\n",
    "\n",
    "# visualize the results\n",
    "result = task.result()\n",
    "counts = result.measurement_counts\n",
    "print('measurement results:', counts)\n",
    "\n",
    "# plot using Counter\n",
    "plt.bar(counts.keys(), counts.values());\n",
    "plt.xlabel('bitstrings');\n",
    "plt.ylabel('counts');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ideally, in the noise-free case, the circuit we defined prepares a Bell-state, and we would expect to measure only '00' and '11' outcomes. However, the presence of noise, in our case a bit flip error, means that sometimes we find the state in '01' and '10' instead.\n",
    "\n",
    "The local simulator is suitable for fast prototyping on small circuits. If you want to run a noisy circuit with more than 10~12 qubits, we recommend using the managed simulator DM1. Using DM1, you can run circuits with up to 17 qubits, and benefit from parallel execution for a group of circuits. The code below shows an example of preparing a 13-qubit GHZ state in the presence of noise."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Defining noise channels <a class=\"anchor\" id=\"noise_channels\"></a>\n",
    "\n",
    "To apply noise to a quantum circuit, first, you need to define the noise channel, which is defined in Kraus representation. We offer many commonly-used noise channels in the `Noise` class of the [Amazon Braket SDK](https://amazon-braket-sdk-python.readthedocs.io/en/latest/_apidoc/braket.circuits.html). In addition, you can also define your own custom noise channel as a list of Kraus operators.\n",
    "\n",
    "### Pre-defined noise channels <a class=\"anchor\" id=\"pre-defined\"></a>\n",
    "\n",
    "The pre-defined single-qubit noise channels include `BitFlip`, `PhaseFlip`, `Depolarizing`, `AmplitudeDamping`, `GeneralizedAmplitudeDamping`, `PhaseDamping` and `PauliChannel`. \n",
    "The pre-defined two-qubit noise channels include `TwoQubitDepolarizing` and `TwoQubitDephasing`. The Kraus representations for all of the pre-defined channels are summarized in the following table.\n",
    "\n",
    "__single-qubit noise channels__\n",
    "\n",
    "| Noise channel  | <div style=\"width:290px\">Kraus representation</div> | Parameter   |\n",
    "|:-------------- |:--------------------------------------------------  |:------------|\n",
    "|   `BitFlip`     |  $(1-p)\\rho$ + $pX\\rho X$| $p$ is the probability of the bit flip noise.   |\n",
    "|   `PhaseFlip`   |  $(1-p)\\rho$ + $pZ\\rho Z$| $p$ is the probability of the phase flip noise. |\n",
    "| `Depolarizing`  |$(1-p)\\rho$ + $p/3(X\\rho X$ + $Y\\rho Y$ + $Z\\rho Z)$|$p$ is the probability of the depolarizing noise (the three possible error cases share the same probability of $p/3$).|\n",
    "|`AmplitudeDamping`|$K_0\\rho K_0^\\dagger$ + $K_1\\rho K_1^\\dagger$|$K_0=[1,0;0,\\sqrt{1-\\gamma}]$, $K_1=[0,\\sqrt{\\gamma};0,0]$, where $\\gamma$ is the rate of amplitude damping.|\n",
    "|`GeneralizedAmplitudeDamping`|$K_0\\rho K_0^\\dagger$ + $K_1\\rho K_1^\\dagger$ + $K_2\\rho K_2^\\dagger$ + $K_3 \\rho K_3^\\dagger$|$K_0=\\sqrt{p}[1,0;0,\\sqrt{1-\\gamma}]$, $K_1=\\sqrt{p}[0,\\sqrt{\\gamma};0,0]$, $K_2=\\sqrt{1-p}[\\sqrt{1-\\gamma},0;0,1]$, $K_3=\\sqrt{1-p}[0,0;\\sqrt{\\gamma},0]$, where $\\gamma$ is the rate of amplitude damping, and $p$ is the probability of the system been excited by the environment [1].|\n",
    "|`PhaseDamping`|$K_0\\rho K_0^\\dagger$ + $K_1 \\rho K_1^\\dagger$|$K_0=[1,0;0,\\sqrt{1-\\gamma}]$, $K_1=[0,0;0,\\sqrt{\\gamma}]$, where $\\gamma$ is the rate of phase damping.|\n",
    "|`PauliChannel`|$(1-p_x-p_y-p_z)\\rho$ + $p_xX\\rho X$ + $p_yY\\rho Y$ + $p_zZ\\rho Z$|$p_x$, $p_y$ and $p_z$ are probabilities for the Pauli X, Y, Z noise respectively.|\n",
    "\n",
    "\n",
    "__two-qubit noise channels__\n",
    "\n",
    "|<div style=\"width:160px\">Noise channel</div>| <div style=\"width:290px\">Kraus representation</div> | Parameter  |\n",
    "|:----------------------- |:--------------------------------------------------  |:------------|\n",
    "|   `TwoQubitDepolarizing`|  $(1-p)\\rho$ + $p/15(IX\\rho IX$ + $IY\\rho IY$ + $IZ\\rho IZ$ + $XI\\rho XI$ +....+ $ZZ\\rho ZZ)$| $p$ is the probability of the two-qubit depolarizing noise (the 15 possible error combinations share the same probability of $p/15$).|\n",
    "|   `TwoQubitDephasing`   |  $(1-p)\\rho$ + $p/3(IZ\\rho IZ$ + $ZI\\rho ZI$ + $ZZ\\rho ZZ)$| $p$ is the probability of the two-qubit dephasing noise (the three possible error combinations share the same probability of $p/3$). |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The following code block takes the example of the bit flip noise channel: $\\rho\\rightarrow(1-p)\\rho$ + $pX\\rho X$, where $p$ corresponds to the `probability` parameter when defining the noise. This noise channel is equivalent to applying a bit flip error (applying an X gate) with probability $p$ and doing nothing with probability $1-p$. You can check the target qubit count and the Kraus operators of the noise channel defined."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "name:  BitFlip\n",
      "qubit count:  1\n",
      "Kraus operators: \n",
      "[[0.9486833+0.j 0.       +0.j]\n",
      " [0.       +0.j 0.9486833+0.j]] \n",
      "\n",
      "[[0.        +0.j 0.31622777+0.j]\n",
      " [0.31622777+0.j 0.        +0.j]] \n",
      "\n"
     ]
    }
   ],
   "source": [
    "# define a bit flip noise channel with probability = 0.1\n",
    "noise = Noise.BitFlip(probability=0.1)\n",
    "\n",
    "print('name: ', noise.name)\n",
    "print('qubit count: ', noise.qubit_count)\n",
    "print('Kraus operators: ')\n",
    "for matrix in noise.to_matrix():\n",
    "    print(matrix, '\\n')\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Other pre-defined noise channels can be used in a similar way:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "# define a phase flip noise channel\n",
    "noise = Noise.PhaseFlip(probability=0.1)\n",
    "# define a single-qubit depolarizing noise channel\n",
    "noise = Noise.Depolarizing(probability=0.1)\n",
    "# define a two-qubit depolarizing noise channel\n",
    "noise = Noise.TwoQubitDepolarizing(probability=0.1)\n",
    "# define a two-qubit dephasing noise channel\n",
    "noise = Noise.TwoQubitDephasing(probability=0.1)\n",
    "# define an amplitude damping noise channel\n",
    "noise = Noise.AmplitudeDamping(gamma=0.1)\n",
    "# define a generalized amplitude damping noise, where gamma is the amplitude damping rate, and\n",
    "# probability is the probability of the system being excited by the environment.\n",
    "noise = Noise.GeneralizedAmplitudeDamping(gamma=0.1, probability=0.1)\n",
    "# define a phase damping noise channel\n",
    "noise = Noise.PhaseDamping(gamma=0.1)\n",
    "# define a Pauli noise channel\n",
    "noise = Noise.PauliChannel(probX=0.1, probY=0.2, probZ=0.3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Defining custom noise channels <a class=\"anchor\" id=\"self-defined\"></a>\n",
    "Apart from the pre-defined noise models, you can also define your own noise model by specifying a list of Kraus operators. The following code shows an example of defining a two-qubit Kraus channel with randomly generated unitary operators."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "# creat an arbitrary 2-qubit Kraus matrix\n",
    "E0 = unitary_group.rvs(4) * np.sqrt(0.2) \n",
    "E1 = unitary_group.rvs(4) * np.sqrt(0.8)\n",
    "K = [E0, E1] \n",
    "\n",
    "# define a two-qubit noise channel with Kraus operators\n",
    "noise = Noise.Kraus(K) "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that the noise channel you define needs to form a CPTP map. If the input matrices do not define a CPTP map, an error will be raised."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The input matrices do not define a completely-positive trace-preserving map.\n"
     ]
    }
   ],
   "source": [
    "K_invalid = [np.random.randn(2,2), np.random.randn(2,2)] \n",
    "\n",
    "try:\n",
    "    noise = Noise.Kraus(K_invalid)\n",
    "    pass\n",
    "except ValueError as err:\n",
    "    print(err)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Adding noise to a circuit <a class=\"anchor\" id=\"apply_noise\"></a>\n",
    "\n",
    "There are two methods to build a 'noisy' circuit. First, you can add noise to the circuit 'bottom-up', by using the noise operations in the same way as you would add a gate to the circuit. Second, you can use the methods `apply_gate_noise()`, `apply_initialization_noise()` and `apply_readout_noise()` to apply gate error, qubit initialization error and measurement error globally to existing circuits. \n",
    "\n",
    "The direct method is more flexible as you can apply noise to any place in a circuit. But for an existing large circuit with lots of gates, you may want to use the global methods to conveniently apply noise to the circuit.\n",
    "\n",
    "\n",
    "### Build noisy circuits bottom-up  <a class=\"anchor\" id=\"apply_noise_directly\"></a>\n",
    "Noise channels can be applied to the circuit the same way as gates. The following example shows how to apply single- and two-qubit noise channels directly to a circuit. The noise applied can be visualized in the circuit diagram with the `print()` method."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "T  : |0|     1     |     2     |\n",
      "                                \n",
      "q0 : -X-C-----------X-DEPH(0.1)-\n",
      "        |             |         \n",
      "q1 : -X-X-DEPO(0.2)---DEPH(0.1)-\n",
      "\n",
      "T  : |0|     1     |     2     |\n"
     ]
    }
   ],
   "source": [
    "# apply depolarizing noise\n",
    "circ = Circuit().x(0).x(1).cnot(0,1).depolarizing(1, probability=0.2).x(0).two_qubit_dephasing(target1=0, target2=1, probability=0.1)\n",
    "print(circ)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Applying noise to existing circuits with global methods<a class=\"anchor\" id=\"apply_noise_globally\"></a>\n",
    "\n",
    "We offer three methods to apply noise globally to the circuit: `apply_gate_noise()`, `apply_initialization_noise()` and `apply_readout_noise()`. In the following, we explain in detail the usage of these three methods.\n",
    "\n",
    "#### Applying gate noise to the circuit <a class=\"anchor\" id=\"gate-noise\"></a>\n",
    "\n",
    "`apply_gate_noise()` is the method to conveniently apply gate-noise to the circuit. It accepts the following input parameters:\n",
    "\n",
    "- __noise__: A single or a list of noise channel in `Noise` type.\n",
    "- __target_unitary__: A single unitary gate in the form of a matrix in `numpy.ndarray` type. The noise will be applied to that unitary gate. \n",
    "- __target_gates__: A single or a list of gates in `Gate` type. Note that `target_gates` and `target_unitary` can not be provided at the same time. If none of `target_gates` and `target_unitary` is given, noise will be applied to all the gates in the circuit. \n",
    "- __target_qubits__: A single or a list of qubit indexes. If not given, noise will be applied to all the qubits in the circuit.\n",
    "\n",
    "When calling the method, the noise channel(s) will be applied right after all `target_gates` in `target_qubits`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-block alert-info\">\n",
    "    <b>Note</b> When you call this method, noise will be inserted right after the gate. If you like to apply more than one noise operation, be aware of the order. Alternatively, you can provide a list of noise operations in one call, and the noise will be applied in forward order. \n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The code below is an example of applying phase damping noise to all gates in the circuit."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Noise is applied to every gate in the circuit:\n",
      "\n",
      "T  : |        0        |    1    |\n",
      "                                  \n",
      "q0 : -X-PD(0.1)-BF(0.1)-C-PD(0.1)-\n",
      "                        |         \n",
      "q2 : -------------------X-PD(0.1)-\n",
      "\n",
      "T  : |        0        |    1    |\n"
     ]
    }
   ],
   "source": [
    "noise = Noise.PhaseDamping(gamma=0.1)\n",
    "\n",
    "# the noise channel is applied to every gate in the circuit\n",
    "circ = Circuit().x(0).bit_flip(0,0.1).cnot(0,2)\n",
    "circ.apply_gate_noise(noise)\n",
    "print('Noise is applied to every gate in the circuit:\\n')\n",
    "print(circ)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you want to apply noise to some particular gates in the circuit, you can specify them as `target_gates`. Below is an example in which noise is applied to all X gates in the circuit."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-block alert-info\">\n",
    "    <b>Note</b> The <code>target_gates</code> must be a <code>Gate</code> type. You can find all available gates with the following commands:\n",
    "    \n",
    "<code>\n",
    "from braket.circuits import Gate\n",
    "gate_set = [attr for attr in dir(Gate) if attr[0] in string.ascii_uppercase]\n",
    "print(gate_set)\n",
    "</code>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Noise is applied to every X gate:\n",
      "\n",
      "T  : |    0    |     1     |2|\n",
      "                              \n",
      "q0 : -X-PD(0.1)-C-------------\n",
      "                |             \n",
      "q1 : -Y---------|-X-PD(0.1)---\n",
      "                |             \n",
      "q2 : -----------X-----------Z-\n",
      "\n",
      "T  : |    0    |     1     |2|\n"
     ]
    }
   ],
   "source": [
    "# the noise channel is applied to all the X gates in the circuit\n",
    "circ = Circuit().x(0).y(1).cnot(0,2).x(1).z(2)\n",
    "circ.apply_gate_noise(noise, target_gates = Gate.X)\n",
    "print('Noise is applied to every X gate:\\n')\n",
    "print(circ)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you define custom unitary gates as part of your circuit, and you want to apply noise to them, you can use the `target_unitary` criterion."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Noise is applied to U2:\n",
      "\n",
      "T  : |0|1| 2 |3|    4    |\n",
      "                          \n",
      "q0 : -X-U-C---------------\n",
      "        | |               \n",
      "q1 : -Y-U-|-X---U-PD(0.1)-\n",
      "          |     |         \n",
      "q2 : -----X---Z-U-PD(0.1)-\n",
      "\n",
      "T  : |0|1| 2 |3|    4    |\n"
     ]
    }
   ],
   "source": [
    "U1=unitary_group.rvs(4)\n",
    "U2=unitary_group.rvs(4)\n",
    "circ = Circuit().x(0).y(1).unitary((0,1),U1).cnot(0,2).x(1).z(2).unitary((1,2),U2)\n",
    "circ.apply_gate_noise(noise, target_unitary = U2)\n",
    "print('Noise is applied to U2:\\n')\n",
    "print(circ)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you want to apply noise to some particular qubits in the circuit, you can specify them as `target_qubits`. Below is an example to apply noise to all gates in qubits 0 and 2 in the circuit."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Noise is applied to every gate in qubits 0 and 2:\n",
      "\n",
      "T  : |    0    |    1    |    2    |\n",
      "                                    \n",
      "q0 : -X-PD(0.1)-C-PD(0.1)-----------\n",
      "                |                   \n",
      "q1 : -Y---------|-X-----------------\n",
      "                |                   \n",
      "q2 : -----------X-PD(0.1)-Z-PD(0.1)-\n",
      "\n",
      "T  : |    0    |    1    |    2    |\n"
     ]
    }
   ],
   "source": [
    "# the noise channel is applied to every gate on qubits 0 and 2\n",
    "circ = Circuit().x(0).y(1).cnot(0,2).x(1).z(2)\n",
    "circ.apply_gate_noise(noise, target_qubits = [0,2])\n",
    "print('Noise is applied to every gate in qubits 0 and 2:\\n')\n",
    "print(circ)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `target_qubits` and `target_gates` criteria can be used at the same time. The code block below applies the gate noise to all X gates in qubit 0."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Noise is applied to X gates in qubits 0:\n",
      "\n",
      "T  : |    0    | 1 |    2    |\n",
      "                              \n",
      "q0 : -X-PD(0.1)-C---X-PD(0.1)-\n",
      "                |             \n",
      "q1 : -Y---------|-X-----------\n",
      "                |             \n",
      "q2 : -----------X---Z---------\n",
      "\n",
      "T  : |    0    | 1 |    2    |\n"
     ]
    }
   ],
   "source": [
    "# the noise channel is applied to X gate on qubits 0\n",
    "circ = Circuit().x(0).y(1).cnot(0,2).x(0).x(1).z(2)\n",
    "circ.apply_gate_noise(noise, target_gates = Gate.X, target_qubits = 0)\n",
    "print('Noise is applied to X gates in qubits 0:\\n')\n",
    "print(circ)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If a list of noise channels is provided, the first noise channel in the list will be applied first, then the second.  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Noise channels are applied to every gate in qubits 0 and 1:\n",
      "\n",
      "T  : |         0         |              1              |2|\n",
      "                                                          \n",
      "q0 : -X-DEPO(0.1)-BF(0.2)-C-DEPO(0.1)-BF(0.2)-------------\n",
      "                          |                               \n",
      "q1 : -Y-DEPO(0.1)-BF(0.2)-|-X---------DEPO(0.1)-BF(0.2)---\n",
      "                          |                               \n",
      "q2 : ---------------------X-----------------------------Z-\n",
      "\n",
      "T  : |         0         |              1              |2|\n"
     ]
    }
   ],
   "source": [
    "# define two noise channels\n",
    "noise1 = Noise.Depolarizing(probability=0.1)\n",
    "noise2 = Noise.BitFlip(probability=0.2)\n",
    "\n",
    "# apply a list of noise channels \n",
    "circ = Circuit().x(0).y(1).cnot(0,2).x(1).z(2)\n",
    "circ.apply_gate_noise([noise1, noise2], target_qubits = [0,1])\n",
    "print('Noise channels are applied to every gate in qubits 0 and 1:\\n')\n",
    "print(circ)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you want to apply multi-qubit noise channels to a gate, the number of qubits associated with the gate must equal to the number of qubits defined by the noise channel, or otherwise the noise will not be applied. Below shows an example."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The two-qubit noise channel is applied to all the two-qubit gates in the circuit:\n",
      "\n",
      "T  : |0|      1      |      2       |\n",
      "                                     \n",
      "q0 : -X-C-DEPH(0.1)---SWAP-DEPH(0.1)-\n",
      "        | |           |    |         \n",
      "q1 : -Y-|-|---------X-SWAP-DEPH(0.1)-\n",
      "        | |                          \n",
      "q2 : ---X-DEPH(0.1)---Z--------------\n",
      "\n",
      "T  : |0|      1      |      2       |\n"
     ]
    }
   ],
   "source": [
    "# define a two-qubit noise channel\n",
    "noise = Noise.TwoQubitDephasing(probability=0.1)\n",
    "\n",
    "# apply the noise to the circuit\n",
    "circ = Circuit().x(0).y(1).cnot(0,2).x(1).z(2).swap(1,0)\n",
    "circ.apply_gate_noise(noise)\n",
    "print('The two-qubit noise channel is applied to all the two-qubit gates in the circuit:\\n')\n",
    "print(circ)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Applying initialization noise to the circuit <a class=\"anchor\" id=\"initialization-noise\"></a>\n",
    "\n",
    "`apply_initialization_noise()` is the method to apply initialization noise to the circuit. By using the method, the noise will be applied to every qubit at the beginning of a circuit. It accepts the following input parameters:\n",
    "\n",
    "- __noise__: a single or a list of noise channel in `Noise` type.\n",
    "- __target_qubits__: a single or a list of qubit indexes. If not given, noise will be applied to all the qubits in the circuit.\n",
    "\n",
    "If you want to apply the initialization noise to an empty circuit, you need to provide `target_qubits` to the method. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-block alert-info\">\n",
    "    <b>Note</b> When you call this method, noise will be inserted at the very beginning of the circuit. If you like to apply more than one noise operation, be aware of the order. Alternatively, you can provide a list of noise operations in one call, and the noise will be applied in forward order. \n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Initialization noise is applied to the circuit:\n",
      "\n",
      "T  : |     0     | 1 |2|\n",
      "                        \n",
      "q0 : -DEPO(0.1)-X-C-----\n",
      "                  |     \n",
      "q1 : -DEPO(0.1)-Y-|-X---\n",
      "                  |     \n",
      "q2 : -DEPO(0.1)---X---Z-\n",
      "\n",
      "T  : |     0     | 1 |2|\n"
     ]
    }
   ],
   "source": [
    "# define a noise channel\n",
    "noise = Noise.Depolarizing(probability=0.1)\n",
    "\n",
    "# the noise channel is applied as the initialization noise to the circuit\n",
    "circ = Circuit().x(0).y(1).cnot(0,2).x(1).z(2)\n",
    "circ.apply_initialization_noise(noise)\n",
    "print('Initialization noise is applied to the circuit:\\n')\n",
    "print(circ)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you want to apply a multi-qubit noise channel as the initialization noise to a circuit and if the number of the qubits in the existing circuit doesn't match the number of qubits as defined by the noise channel, you need to provide `target_qubits` with the number of qubits matching the noise channel. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Initialization noise is applied to the circuit:\n",
      "\n",
      "T  : |     0     |1|2|\n",
      "                      \n",
      "q0 : -DEPH(0.1)-X-C-Z-\n",
      "      |           |   \n",
      "q1 : -DEPH(0.1)-Y-X-X-\n",
      "\n",
      "T  : |     0     |1|2|\n"
     ]
    }
   ],
   "source": [
    "# define a two-qubit noise channel\n",
    "noise = Noise.TwoQubitDephasing(probability=0.1)\n",
    "\n",
    "# the noise channel is applied as the initialization noise to the circuit\n",
    "circ = Circuit().x(0).y(1).cnot(0,1).x(1).z(0)\n",
    "circ.apply_initialization_noise(noise)\n",
    "print('Initialization noise is applied to the circuit:\\n')\n",
    "print(circ)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Applying readout noise to the circuit <a class=\"anchor\" id=\"readout-noise\"></a>\n",
    "\n",
    "The method of `apply_readout_noise()` is very similar to the method to apply initialization noise, except that the noise channel is applied to every qubit in the end of a circuit. It accepts the following input parameters:\n",
    "\n",
    "- __noise__: a single or a list of noise channel in `Noise` type.\n",
    "- __target_qubits__: a single or a list of qubit indexes. If not given, noise will be applied to all the qubits in the circuit.\n",
    "\n",
    "If you want to apply the readout noise to an empty circuit, you need to provide `target_qubits` to the method. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-block alert-info\">\n",
    "    <b>Note</b> When you call this method, noise will be inserted at the very end of the circuit. If you like to apply more than one noise operation, be aware of the order. You can also provide a list of noise operations in the one call, and the noise will be applied in forward order. \n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Read-out noise is applied to the circuit:\n",
      "\n",
      "T  : |0| 1 |         2         |\n",
      "                                \n",
      "q0 : -X-C---DEPO(0.1)-----------\n",
      "        |                       \n",
      "q1 : -Y-|-X-DEPO(0.1)-----------\n",
      "        |                       \n",
      "q2 : ---X---Z---------DEPO(0.1)-\n",
      "\n",
      "T  : |0| 1 |         2         |\n"
     ]
    }
   ],
   "source": [
    "# define a noise channel\n",
    "noise = Noise.Depolarizing(probability=0.1)\n",
    "\n",
    "# the noise channel is applied as the readout noise to the circuit\n",
    "circ = Circuit().x(0).y(1).cnot(0,2).x(1).z(2)\n",
    "circ.apply_readout_noise(noise)\n",
    "print('Read-out noise is applied to the circuit:\\n')\n",
    "print(circ)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you want to apply a multi-qubit noise channel as the readout noise to a circuit and if the number of the qubits in the existing circuit doesn't match the number of qubits as defined by the noise channel, you need to provide `target_qubits` with the number of qubits matching the noise channel. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Using both the direct and global methods to apply noise <a class=\"anchor\" id=\"both\"></a>\n",
    "You can apply noise to the circuit using both the direct and global methods. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Noise channels are applied to the circuit:\n",
      "\n",
      "T  : |        0        |     1     |2|\n",
      "                                      \n",
      "q0 : -X-PF(0.2)-BF(0.1)---------------\n",
      "                                      \n",
      "q1 : -Y-----------------C-DEPO(0.1)---\n",
      "                        | |           \n",
      "q2 : -------------------X-DEPO(0.1)-Z-\n",
      "\n",
      "T  : |        0        |     1     |2|\n"
     ]
    }
   ],
   "source": [
    "# define a noise channel\n",
    "noise = Noise.PhaseFlip(probability=0.2)\n",
    "\n",
    "# create a circuit and add noise directly to the circuit\n",
    "circ = Circuit().x(0).y(1).bit_flip(0,0.1).cnot(1,2).two_qubit_depolarizing(1, 2, probability=0.1).z(2)\n",
    "circ.apply_gate_noise(noise, target_qubits=0)\n",
    "print('Noise channels are applied to the circuit:\\n')\n",
    "print(circ)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Running a noisy circuit <a class=\"anchor\" id=\"run\"></a>\n",
    "\n",
    "Running a noisy circuit is like running any other task on Amazon Braket. In the example below we will pick the local simulator to run our circuit. \n",
    "\n",
    "With shots = 0, you can obtain the exact values of probability, density matrix and expectation values of the mixed state by attaching the corresponding result type. The reduced density matrix is also available if providing the targets qubits. If no target qubit is provided, the full density matrix will be returned. \n",
    "\n",
    "An example is shown in the code block below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "T  : |    0    |    1    |    2    |              Result Types              |\n",
      "                                                                             \n",
      "q0 : -X-AD(0.1)-C-AD(0.1)-----------Probability-Expectation(Z)-DensityMatrix-\n",
      "                |                   |                          |             \n",
      "q1 : -Y---------|-X-----------------Probability----------------DensityMatrix-\n",
      "                |                   |                                        \n",
      "q2 : -----------X-AD(0.1)-Z-AD(0.1)-Probability------------------------------\n",
      "\n",
      "T  : |    0    |    1    |    2    |              Result Types              |\n",
      "- Probability is: \n",
      "[0.1171 0.0729 0.     0.     0.1539 0.6561 0.     0.    ]\n",
      "- Expectation value <Z_0> is: \n",
      "-0.6199999999999997\n",
      "- The reduced Density Matrix is: \n",
      "[[0.19+0.j 0.  +0.j 0.  +0.j 0.  +0.j]\n",
      " [0.  +0.j 0.  +0.j 0.  +0.j 0.  +0.j]\n",
      " [0.  +0.j 0.  +0.j 0.81+0.j 0.  +0.j]\n",
      " [0.  +0.j 0.  +0.j 0.  +0.j 0.  +0.j]]\n"
     ]
    }
   ],
   "source": [
    "# define the noise channel\n",
    "noise = Noise.AmplitudeDamping(gamma=0.1)\n",
    "# create a circuit\n",
    "circ = Circuit().x(0).y(1).cnot(0,2).x(1).z(2)\n",
    "# apply the noise to qubits 0 and 2 in the circuit \n",
    "circ.apply_gate_noise(noise, target_qubits = [0,2])\n",
    "\n",
    "# attach the result types\n",
    "circ.probability()\n",
    "circ.expectation(observable = Observable.Z(),target=0)\n",
    "# attach the density matrix with target=[0,1], and the reduced density matrix of qubits 0,1 will be returned\n",
    "circ.density_matrix(target=[0,1])\n",
    "print(circ)\n",
    "\n",
    "# choose the noise simualtor, which is called \"braket_dm\"\n",
    "device = LocalSimulator(\"braket_dm\")\n",
    "# run the circuit\n",
    "task = device.run(circ, shots=0)\n",
    "result = task.result()\n",
    "\n",
    "\n",
    "print('- Probability is: ')\n",
    "print(result.values[0])\n",
    "print('- Expectation value <Z_0> is: ')\n",
    "print(result.values[1])\n",
    "print('- The reduced Density Matrix is: ')\n",
    "print(result.values[2])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "With shots > 0, the results are sampled from the probability distributions. The result type `density_matrix` is not available for shots > 0. \n",
    "\n",
    "The code below shows the expectation value $\\langle Z_0\\rangle$ and the probability that the mixed state collapsing into different states. We see those values here are different from the exact values obtained in the shots = 0 case."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "T  : |    0    |    1    |    2    |       Result Types       |\n",
      "                                                               \n",
      "q0 : -X-AD(0.1)-C-AD(0.1)-----------Probability-Expectation(Z)-\n",
      "                |                   |                          \n",
      "q1 : -Y---------|-X-----------------Probability----------------\n",
      "                |                   |                          \n",
      "q2 : -----------X-AD(0.1)-Z-AD(0.1)-Probability----------------\n",
      "\n",
      "T  : |    0    |    1    |    2    |       Result Types       |\n",
      "- Probability is: \n",
      "[0.14 0.08 0.   0.   0.18 0.6  0.   0.  ]\n",
      "- Expectation value <Z_0> is: \n",
      "-0.56\n"
     ]
    }
   ],
   "source": [
    "# create a circuit\n",
    "circ = Circuit().x(0).y(1).cnot(0,2).x(1).z(2)\n",
    "circ.apply_gate_noise(noise, target_qubits = [0,2])\n",
    "\n",
    "circ.probability()\n",
    "circ.expectation(observable = Observable.Z(),target=0)\n",
    "print(circ)\n",
    "\n",
    "# run the circuit\n",
    "task = device.run(circ, shots=100)\n",
    "result = task.result()\n",
    "\n",
    "print('- Probability is: ')\n",
    "print(result.values[0])\n",
    "print('- Expectation value <Z_0> is: ')\n",
    "print(result.values[1])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Reference\n",
    "[1] Srikanth R, Banerjee S. \"Squeezed generalized amplitude damping channel\", Physical Review A, 2008, 77(1): 012318."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "conda_braket",
   "language": "python",
   "name": "conda_braket"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
